#pragma region Licensing
/*
       LOGL::GLib -> LorgonJortle's OpenGL Game Library
       Copyright (C) 2011  Jesse 'LorgonJortle' Wilkerson

       This program is free software: you can redistribute it and/or modify
       it under the terms of the GNU General Public License as published by
       the Free Software Foundation, either version 3 of the License, or
       (at your option) any later version.

       This program is distributed in the hope that it will be useful,
       but WITHOUT ANY WARRANTY; without even the implied warranty of
       MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
       GNU General Public License for more details.

       You should have received a copy of the GNU General Public License
       along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#pragma endregion

#include "SpriteBatch.h"

namespace LOGL_GLib
{
     namespace Common
     {
          /* Initialize our static variables. */
          GLuint SpriteBatch::VBO = 0;
          GLboolean SpriteBatch::isSetup = GL_FALSE;
          Shader SpriteBatch::shader;
          GLuint SpriteBatch::shaderProjection;
          GLuint SpriteBatch::shaderModelView;

          /******************************************************************************
           SpriteBatch::SpriteBatch();
                    Arguments:
                         void

                    Description:
                         Constructs a SpriteBatch object.

                    Returns:
                         The SpriteBatch object.
          *******************************************************************************/
          SpriteBatch::SpriteBatch()
          {
               projection = Matrix::CreateOrthoProjection(0.0f, 1.0f, 1.0f, 0.0f, 1.0f, 10.0f);
          }

          /******************************************************************************
           SpriteBatch::~SpriteBatch();
                    Arguments:
                         void

                    Description:
                         Destructs the SpriteBatch, releasing any indebted resources.

                    Returns:
                         void
          *******************************************************************************/
          SpriteBatch::~SpriteBatch()
          {

          }

          /******************************************************************************
           GLboolean SpriteBatch::Initialize();
                    Arguments:
                         void

                    Description:
                         Initializes batch rendering.

                    Returns:
                         Whether or not batch rendering was successfully initialized.
          *******************************************************************************/
          GLboolean SpriteBatch::Initialize(ShaderManager& _Shaders)
          {
               /* Delete any existing buffer. */
               if(glIsBuffer(VBO))
                    glDeleteBuffers(1, &VBO);

               /* Quad of points and texcoords. */
               typedef struct { Vector3 pos; Vector2 tex; } PosTex;
               PosTex quad[4];

               /* Top Left. */
               quad[0].pos = Vector3(0.0f, 0.0f, 0.0f);
               quad[0].tex = Vector2(0.0f, 0.0f);

               /* Bottom Left. */
               quad[1].pos = Vector3(0.0f, -1.0f, 0.0f);
               quad[1].tex = Vector2(0.0f, 1.0f);

               /* Bottom Right. */
               quad[2].pos = Vector3(1.0f, -1.0f, 0.0f);
               quad[2].tex = Vector2(1.0f, 1.0f);

               /* Top Right. */
               quad[3].pos = Vector3(1.0f, 0.0f, 0.0f);
               quad[3].tex = Vector2(1.0f, 0.0f);

               /* Create a VBO for the quad to stay in. */
               glGenBuffers(1, &VBO);
               glBindBuffer(GL_ARRAY_BUFFER, VBO);
               glBufferData(GL_ARRAY_BUFFER, sizeof(quad), quad, GL_STATIC_DRAW);


               /* Create our shader. */
               shader.Name = "Font Rendering";
               if(shader.Load(FromMemory, Shader_Texture_Vert, 0, Shader_Texture_Frag))
                    if(shader.Compile())
                    {
                         _Shaders.Add(shader);
                         _Shaders.Bind(shader.ID);
                         shaderProjection = shader.GetUniformLocation("Projection");
                         shaderModelView = shader.GetUniformLocation("ModelView");
                    }


               /* We're done. */
               return GL_TRUE;
          }

          /******************************************************************************
           GLvoid SpriteBatch::Cleanup();
                    Arguments:
                         void

                    Description:
                         Cleans up batch rendering.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid SpriteBatch::Cleanup()
          {
               /* Delete any existing buffer. */
               if(glIsBuffer(VBO))
                    glDeleteBuffers(1, &VBO);
          }

          /******************************************************************************
           GLboolean SpriteBatch::Begin(SDL_Surface* _Screen);
                    Arguments:
                         SDL_Surface* _Screen:
                              The screen we will be drawing to.

                    Description:
                         Sets up orthogonal rendering.

                    Returns:
                         Whether or not the batch is ready to render.
          *******************************************************************************/
          GLboolean SpriteBatch::Begin(SDL_Surface* _Screen)
          {
               /* Store our screen. */
               screen = _Screen;

               /* Calculate the orthographic projection matrix based on the size of the screen. */
               projection.Identity();
               projection = Matrix::CreateOrthoProjection(0.0f, screen->w, 0.0f, screen->h, -1.0f, 1.0f);

               /* Upload the new projection matrix. */
               glUseProgram(shader.ProgramID);
               glUniformMatrix4fv(shaderProjection, 1, GL_FALSE, &projection[0][0]);

               /* Initialize our modelView matrix. */
               modelView.Identity();

               /* No depth testing here. */
               glDisable(GL_DEPTH_TEST);

               /* We're setup now. */
               isSetup = GL_TRUE;

               return isSetup;
          }

          /******************************************************************************
           GLvoid SpriteBatch::End();
                    Arguments:
                         void

                    Description:
                         Returns from orthogonal rendering.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid SpriteBatch::End()
          {
               /* We're no longer setup. */
               isSetup = GL_FALSE;

               /* Get depth testing back up. */
               glEnable(GL_DEPTH_TEST);

               /* Disable our ortho shader. */
               glUseProgram(0);
          }

          /******************************************************************************
           GLvoid SpriteBatch::DrawString2(const Font& _Font, const std::string& _Text,
                                   const Vector2& _Position, SDL_Color _Color);
                    Arguments:
                         const Font& _Font:
                              The typeface to render the string with.
                         const std::string& _Text:
                              The string to render.
                         const Vector2& _Position:
                              Where (in screen coordinates) to render the text.
                         SDL_Color _Color:
                              The color to render the string.

                    Description:
                         Renders the specified string with the specified font in screen coordinates.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid SpriteBatch::DrawString2(const Font& _Font, const std::string& _Text, const Vector2& _Position, SDL_Color _Color)
          {
               /* Only bother if we've been setup properly. */
               if(!isSetup)
                    return;

               /* Attempt to render the text to a surface. */
               SDL_Surface* surface = RenderSurface(_Font, _Text, _Color);

               /* Attempt to load the surface into an OpenGL texture. */
               if(texture.Load(surface))
               {
                    /* The texture loaded. Let's draw out quad. */
                    glBindBuffer(GL_ARRAY_BUFFER, VBO);
                    glVertexAttribPointer(Attribute_Position, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
                    glVertexAttribPointer(Attribute_Texture0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
                    glEnableVertexAttribArray(Attribute_Position);
                    glEnableVertexAttribArray(Attribute_Texture0);

                    /* TODO: Change Texture2 to accept texture parameters. */
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

                    /* Enable transparency. */
                    glEnable(GL_BLEND);
                    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                    /* Bind the texture. */
                    texture.Bind(GL_TEXTURE0);

                    /* Correctly position the font. */
                    modelView.Identity();
                    modelView.Translate(Vector3(_Position.X, _Position.Y, 0.0f));
                    modelView.Scale(Vector3(surface->w, surface->h, 1.0f));
                    glUniformMatrix4fv(shaderModelView, 1, GL_FALSE, &modelView[0][0]);

                    /* Draw our quad. */
                    glDrawArrays(GL_QUADS, 0, 4);

                    /* Unbind our texture. */
                    texture.Unbind();

                    /* Disable transparency. */
                    glDisable(GL_BLEND);

                    /* Disable attributes. */
                    glDisableVertexAttribArray(Attribute_Position);
                    glDisableVertexAttribArray(Attribute_Texture0);
               }

               /* Free up the surface we used. */
               SDL_FreeSurface(surface);
          }

          /******************************************************************************
           GLvoid SpriteBatch::DrawString3(const Font& _Font, const std::string& _Text,
                                   SDL_Color _Color);
                    Arguments:
                         const Font& _Font:
                              The typeface to render the string with.
                         const std::string& _Text:
                              The string to render.
                         SDL_Color _Color:
                              The color to render the string.

                    Description:
                         Renders the specified string with the specified font in game coordinates.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid SpriteBatch::DrawString3(const Font& _Font, const std::string& _Text, SDL_Color _Color)
          {
               /* Attempt to render the text to a surface. */
               SDL_Surface* surface = RenderSurface(_Font, _Text, _Color);

               /* Attempt to load the surface into an OpenGL texture. */
               if(texture.Load(surface))
               {
                    /* The texture loaded. Let's draw out quad. */
                    glBindBuffer(GL_ARRAY_BUFFER, VBO);
                    glVertexAttribPointer(Attribute_Position, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
                    glVertexAttribPointer(Attribute_Texture0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
                    glEnableVertexAttribArray(Attribute_Position);
                    glEnableVertexAttribArray(Attribute_Texture0);

                    /* TODO: Change Texture2 to accept texture parameters. */
                    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
                    glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

                    /* Enable transparency. */
                    glEnable(GL_BLEND);
                    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

                    /* Bind the texture. */
                    texture.Bind(GL_TEXTURE0);

                    /* Draw our quad. */
                    glDrawArrays(GL_QUADS, 0, 4);

                    /* Unbind our texture. */
                    texture.Unbind();

                    /* Disable transparency. */
                    glDisable(GL_BLEND);

                    /* Disable attributes. */
                    glDisableVertexAttribArray(Attribute_Position);
                    glDisableVertexAttribArray(Attribute_Texture0);
               }

               /* Free up the surface we used. */
               SDL_FreeSurface(surface);
          }

          /******************************************************************************
           GLvoid SpriteBatch::DrawTexture(const Texture2& _Texture,
                                           const Vector2& _Position);
                    Arguments:
                         const Texture2& _Texture:
                              The texture to draw.
                         const Vector2& _Position:
                              The position to draw the texture at (in screen coordinates).

                    Description:
                         Renders the specified texture to the screen.

                    Returns:
                         void
          *******************************************************************************/
          GLvoid SpriteBatch::DrawTexture(const Texture2& _Texture, const Vector2& _Position)
          {
               /* Get our VBO going. */
               glBindBuffer(GL_ARRAY_BUFFER, VBO);
               glVertexAttribPointer(Attribute_Position, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), 0);
               glVertexAttribPointer(Attribute_Texture0, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(GLfloat), (GLvoid*)(3 * sizeof(GLfloat)));
               glEnableVertexAttribArray(Attribute_Position);
               glEnableVertexAttribArray(Attribute_Texture0);

               /* TODO: Change Texture2 to accept texture parameters. */
               glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
               glTexParameteri (GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);

               /* Enable transparency. */
               glEnable(GL_BLEND);
               glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

               /* Correctly position the texture. */
               modelView.Identity();
               modelView.Translate(Vector3(_Position.X, _Position.Y, 0.0f));
               modelView.Scale(Vector3(_Texture.Width, _Texture.Height, 1.0f));
               glUniformMatrix4fv(shaderModelView, 1, GL_FALSE, &modelView[0][0]);

               /* Bind the texture. */
               _Texture.Bind(GL_TEXTURE0);

               /* Draw our quad. */
               glDrawArrays(GL_QUADS, 0, 4);

               /* Unbind our texture. */
               _Texture.Unbind();

               /* Disable transparency. */
               glDisable(GL_BLEND);

               /* Disable attributes. */
               glDisableVertexAttribArray(Attribute_Position);
               glDisableVertexAttribArray(Attribute_Texture0);
          }

          /******************************************************************************
           SDL_Surface* SpriteBatch::RenderSurface(const Font& _Font,
                                                   const std::string& _Text, SDL_Color _Color);
                    Arguments:
                         const Font& _Font:
                              The font to render with.
                         const std::string& _Text:
                              The text to render with the font.
                         SDL_Color _Color:
                              The color to render to text.

                    Description:
                         Renders the specified string to an SDL surface.

                    Returns:
                         A texture of the specified string rendered in the specified font.
          *******************************************************************************/
          SDL_Surface* SpriteBatch::RenderSurface(const Font& _Font, const std::string& _Text, SDL_Color _Color)
          {
               /* Change the color we received to a bright red. */
               _Color.r = 0;
               _Color.g = 255;
               _Color.b = 0;

               /* In case we're using a background, set a default color. */
               SDL_Color back;
               back.r = 100;
               back.g = 0;
               back.b = 0;

               /* Render the given text string and return the surface. */
               return TTF_RenderText_Blended(_Font.TypeFace, _Text.c_str(), _Color);
          }

     } /* Namespace Common. */
} /* Namespace LOGL_GLib. */
